package com.sc.nft.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.sc.nft.config.Bank4ValidateConfig;
import com.sc.nft.dao.*;
import com.sc.nft.dto.Card4ValidateDTO;
import com.sc.nft.entity.*;
import com.sc.nft.entity.vo.ApplyBindCardVO;
import com.sc.nft.entity.vo.CardInfoVO;
import com.sc.nft.enums.*;
import com.sc.nft.exception.GlobalRunTimeException;
import com.sc.nft.feign.ShuJuBaoCloudFeign;
import com.sc.nft.helper.LlPayHelper;
import com.sc.nft.pool.UserPool;
import com.sc.nft.service.UserBankCardService;
import com.sc.nft.sup.ErrorCode;
import com.sc.nft.util.BankCardUtil;
import com.sc.nft.util.BankUtil;
import com.sc.nft.util.OrderNoUtil;
import com.sc.nft.vo.Card4ValidateVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.misc.BASE64Encoder;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.sc.nft.config.sand.SandPayUtil.*;

/**
 * 用户银行卡表服务接口实现
 *
 * @author zxy
 * @since 2022-08-31 00:56:48
 * @description 由 Mybatisplus Code Generator 创建
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserBankCardServiceImpl implements UserBankCardService {
    private final UserBankCardDao userBankCardDao;
    private final BankBindApplyDao bankBindApplyDao;
    private final IdcardInfoDao idcardInfoDao;
    private final LlPayHelper llPayHelper;
    private final Bank4ValidateConfig bank4ValidateConfig;
    private final Redisson redisson;
    private final ShuJuBaoCloudFeign shuJuBaoCloudFeign;

    @Override
    public ApplyBindCardVO applyBindCard(UserInfo userInfo, String cardNo, String phoneNo , PayChannelEnum channel) {
        if(!userInfo.getIsSm()){
            throw new GlobalRunTimeException(ErrorCode.PLEASE_PERFORM_REAL_NAME_AUTHENTICATION_FIRST);
        }

        IdcardInfo idcardInfo = idcardInfoDao.getByUserIdAndIsPass(userInfo.getId());
        if(ObjectUtil.isNull(idcardInfo)){
            throw new GlobalRunTimeException("未查询到实名信息");
        }
        UserBankCard userCard = userBankCardDao.getByUserIdAndChannelAndCardNo(userInfo.getId(), channel, cardNo);
        if(ObjectUtil.isNotNull(userCard)){
            throw new GlobalRunTimeException("该银行卡已绑定");
        }
        BankBindApply bankBindApply = new BankBindApply();
        bankBindApply.setUserId(userInfo.getId());
        bankBindApply.setStatus(BankBindApplyStatus.NOT_APPROVAL);
        bankBindApply.setApplyNo(OrderNoUtil.getOrderNo());
        bankBindApply.setType(BankBindApplyType.BIND);
        bankBindApply.setCardNo(cardNo);
        bankBindApply.setChannel(channel);
        bankBindApply.setName(idcardInfo.getName());
        bankBindApply.setCardType(CardTypeEnum.PAY);
        bankBindApply.setPhoneNo(phoneNo);
        bankBindApply.setCertificateNo(idcardInfo.getCardId().toUpperCase(Locale.ROOT));
        bankBindApply.setBankName(BankCardUtil.getBankNameByCardNo(cardNo));
        switch (channel) {
            case SAND:
                JSONObject header = getHeader(APPLY_BIND_METHOD,BIND_CARD);
                JSONObject body = getBindCardRequest(bankBindApply,userInfo);
                bankBindApply.setRequestBody(body.toJSONString());
                JSONObject result = fastPayRequest(header,body,APPLY_BIND_PATH);
                if(ObjectUtil.isEmpty(result)){
                    throw new GlobalRunTimeException(ErrorCode.REQUEST_FAILED_PLEASE_TRY_AGAIN_LATER);
                }
                bankBindApply.setResultBody(result.toJSONString());
                if(!StrUtil.equals("000000",result.getJSONObject("head").getString("respCode"))){
                    throw new GlobalRunTimeException(result.getJSONObject("head").getString("respMsg"));
                }
                bankBindApply.setSdMsgNo(result.getJSONObject("body").getString("sdMsgNo"));
                break;
            case LL_PAY:
                result = llPayHelper.anonymousBindCard(userInfo,bankBindApply);
                bankBindApply.setSdMsgNo(result.getString("token"));
                break;
            default:
                throw new GlobalRunTimeException("该渠道暂不支持绑卡");
        }
        bankBindApply.insert();
        ApplyBindCardVO applyBindCardVO = new ApplyBindCardVO();
        applyBindCardVO.setId(bankBindApply.getId());
        return applyBindCardVO;
    }

    @Override
    public void confirmBindCard(UserInfo userInfo, Long applyId, String smsCode) {
        BankBindApply bankBindApply = bankBindApplyDao.getById(applyId);
        if(ObjectUtil.isNull(bankBindApply)||bankBindApply.getUserId().longValue()!=userInfo.getId().longValue()){
            throw new GlobalRunTimeException(ErrorCode.APPLY_NOT_FOUND);
        }
        switch (bankBindApply.getChannel()) {
            case SAND:
                if (StringUtils.isBlank(smsCode)) {
                    throw new GlobalRunTimeException("验证码为空");
                }
                JSONObject header = getHeader(CONFIRM_BIND_METHOD,BIND_CARD);
                JSONObject body = getConfirmCardRequest(bankBindApply,userInfo,smsCode);
                JSONObject result = fastPayRequest(header,body,CONFIRM_BIND_PATH);
                bankBindApply.setResultBody(result.toJSONString());
                if(StrUtil.equals("000000",result.getJSONObject("head").getString("respCode"))){
                    UserBankCard userBankCard = new UserBankCard();
                    userBankCard.setUserId(bankBindApply.getUserId());
                    userBankCard.setBankCardNo(bankBindApply.getCardNo());
                    userBankCard.setName(bankBindApply.getName());
                    userBankCard.setMobile(bankBindApply.getPhoneNo());
                    userBankCard.setChannel(PayChannelEnum.SAND);
                    userBankCard.setIdentityNo(bankBindApply.getCertificateNo());
                    userBankCard.setBindId(bankBindApply.getId());
                    userBankCard.setSandBid(result.getJSONObject("body").getString("bid"));
                    userBankCard.setBankName(bankBindApply.getBankName());
                    userBankCard.insert();
                    bankBindApply.setBankCardId(userBankCard.getId());
                    bankBindApply.setStatus(BankBindApplyStatus.SUCCESS);
                    bankBindApply.updateById();
                }else{
                    bankBindApply.setStatus(BankBindApplyStatus.FAIL);
                    bankBindApply.updateById();
                    throw new GlobalRunTimeException(result.getJSONObject("head").getString("respMsg"));
                }
                break;
            case LL_PAY:
                if (StringUtils.isBlank(smsCode)) {
                    throw new GlobalRunTimeException("验证码为空");
                }
                try{
                    result = llPayHelper.anonymousBindVerify(userInfo, bankBindApply, smsCode);
                    UserBankCard userBankCard = new UserBankCard();
                    userBankCard.setUserId(bankBindApply.getUserId());
                    userBankCard.setBankCardNo(bankBindApply.getCardNo());
                    userBankCard.setBindId(bankBindApply.getId());
                    userBankCard.setName(bankBindApply.getName());
                    userBankCard.setMobile(bankBindApply.getPhoneNo());
                    userBankCard.setChannel(PayChannelEnum.LL_PAY);
                    userBankCard.setIdentityNo(bankBindApply.getCertificateNo());
                    userBankCard.setSandBid(result.getString("linked_agrtno"));
                    userBankCard.setBankName(bankBindApply.getBankName());
                    userBankCard.insert();
                } catch (GlobalRunTimeException e){
                    log.warn("连连支付:绑卡验证失败:",e);
                    bankBindApply.setStatus(BankBindApplyStatus.FAIL);
                    bankBindApply.updateById();
                    throw e;
                }
                break;
        }
    }

    @Override
    public List<UserBankCard> getByUserId(Long userId) {
        List<UserBankCard> bankCardList = userBankCardDao.getByUserId(userId);
        ArrayList<UserBankCard> list = Lists.newArrayList();
        for (UserBankCard userBankCard : bankCardList) {
            String bankCardNo = userBankCard.getBankCardNo();
            String cardNo = DesensitizedUtil.idCardNum(bankCardNo, 0, 4);
            userBankCard.setBankCardNo(cardNo.substring(cardNo.length() - 8));
            list.add(userBankCard);
        }
        return list;
    }

    @Override
    public List<UserBankCard> getByUserIdAndChannel(Long userId,PayChannelEnum channel) {
        List<UserBankCard> bankCardList = userBankCardDao.getByUserIdAndChannel(userId,channel);
        ArrayList<UserBankCard> list = Lists.newArrayList();
        for (UserBankCard userBankCard : bankCardList) {
            String bankCardNo = userBankCard.getBankCardNo();
            String cardNo = "****"+bankCardNo.substring(bankCardNo.length()-4);
            userBankCard.setBankCardNo(cardNo);
            list.add(userBankCard);
        }
        return list;
    }


    @Override
    public void unBind(UserInfo userInfo, Long cardId) {
        UserBankCard userBankCard = userBankCardDao.getById(cardId);
        if(ObjectUtil.isNull(userBankCard)||userBankCard.getUserId().longValue()!=userInfo.getId().longValue()){
            throw new GlobalRunTimeException(ErrorCode.CARD_NOT_FOUND);
        }
        BankBindApply bankBindApply = new BankBindApply();
        bankBindApply.setUserId(userInfo.getId());
        bankBindApply.setChannel(userBankCard.getChannel());
        bankBindApply.setBankCardId(userBankCard.getId());
        bankBindApply.setApplyNo(OrderNoUtil.getOrderNo());
        bankBindApply.setType(BankBindApplyType.DIS_BIND);
        bankBindApply.setCardNo(userBankCard.getBankCardNo());
        bankBindApply.setName(userBankCard.getName());
        bankBindApply.setPhoneNo(userBankCard.getMobile());
        bankBindApply.setCertificateNo(userBankCard.getIdentityNo());
        switch (bankBindApply.getChannel()) {
            case SAND:
                JSONObject header = getHeader(UN_BIND_METHOD,BIND_CARD);
                JSONObject body = getUnBindRequest(bankBindApply,userInfo,userBankCard.getSandBid());
                JSONObject result = fastPayRequest(header,body,UN_BIND_PATH);
                bankBindApply.setResultBody(result.toJSONString());
                log.info("支付卡解绑:{},请求参数header:{},请求参数body:{}",result,header,body);
                if(StrUtil.equals("000000",result.getJSONObject("head").getString("respCode"))){
                    bankBindApply.setStatus(BankBindApplyStatus.SUCCESS);
                    bankBindApply.setSdMsgNo(result.getJSONObject("body").getString("sdMsgNo"));
                    bankBindApply.insert();
                    userBankCard.deleteById();
                }else{
                    bankBindApply.setStatus(BankBindApplyStatus.FAIL);
                    bankBindApply.insert();
                    throw new GlobalRunTimeException(result.getJSONObject("head").getString("respMsg"));
                }
                break;
            case LL_PAY:
                try{
                    llPayHelper.unAnonymousBindCard(userInfo,bankBindApply.getApplyNo(), userBankCard);
                    bankBindApply.setStatus(BankBindApplyStatus.SUCCESS);
                    bankBindApply.insert();
                    userBankCard.deleteById();
                }catch (GlobalRunTimeException e){
                    bankBindApply.setResultBody(e.getMessage());
                    bankBindApply.setStatus(BankBindApplyStatus.FAIL);
                    bankBindApply.insert();
                    throw e;
                }
            case HNA_PAY:
                bankBindApply.setStatus(BankBindApplyStatus.SUCCESS);
                bankBindApply.insert();
                userBankCard.deleteById();
                break;
            default:
                throw new GlobalRunTimeException("暂不支持该类型银行卡解绑");
        }

    }

    @Override
    public CardInfoVO getCardInfo(String cardNo) {
        //todo 银行卡格式校验
        Assert.isTrue(BankCardUtil.checkBankCard(cardNo),()-> new GlobalRunTimeException(ErrorCode.BANK_CARD_ERROR));

        String bankNameUrl="https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&cardNo="+cardNo+"&cardBinCheck=true";
        String result= HttpUtil.get(bankNameUrl);
        if(StrUtil.isBlank(result)){
            throw new GlobalRunTimeException("暂时不支持该银行卡绑定");
        }

        JSONObject jsonObject = JSONObject.parseObject(result);
        String bank = jsonObject.getString("bank");
        String cardType = jsonObject.getString("cardType");
        if(!"DC".equals(cardType)){
            throw new GlobalRunTimeException("暂时只支持储蓄卡绑定");
        }

//        BankDetails bankDetails = bankDetailsDao.getByBankNameEnAndPay(bank,bindChannel,Boolean.TRUE);
//        if(ObjectUtil.isEmpty(bankDetails)){
//            throw new GlobalRunTimeException("暂时不支持该银行卡绑定");
//        }

        CardInfoVO cardInfoVO = new CardInfoVO();
        cardInfoVO.setBankName(BankCardUtil.getBankNameByCardNo(cardNo));
//        cardInfoVO.setBankIcon(bankDetails.getBankIcon());
//        cardInfoVO.setBankId(bankDetails.getId());
        return cardInfoVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void hnaBindCard(UserInfo userInfo, String cardNo, String phoneNo) {
        RLock lock = redisson.getLock(RedisKeyEnum.USER_HNA_BIND_CARD_KEY.getKey() + PayChannelEnum.HNA_PAY.getCode() + cardNo);
        try{
            if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
                log.debug("----------------当前线程：{}", Thread.currentThread().getName());
                if(!userInfo.getIsSm()){
                    throw new GlobalRunTimeException(ErrorCode.PLEASE_PERFORM_REAL_NAME_AUTHENTICATION_FIRST);
                }
                IdcardInfo idcardInfo = idcardInfoDao.getByUserIdAndIsPass(userInfo.getId());
                if(ObjectUtil.isNull(idcardInfo)){
                    throw new GlobalRunTimeException("未查询到实名信息");
                }
                UserBankCard userCard = userBankCardDao.getByUserIdAndChannelAndCardNo(userInfo.getId(), PayChannelEnum.HNA_PAY, cardNo);
                if(ObjectUtil.isNotNull(userCard)){
                    throw new GlobalRunTimeException("该银行卡已绑定");
                }
                BankBindApply bankBindApply = new BankBindApply();
                bankBindApply.setUserId(userInfo.getId());
                bankBindApply.setStatus(BankBindApplyStatus.NOT_APPROVAL);
                bankBindApply.setApplyNo(OrderNoUtil.getOrderNo());
                bankBindApply.setType(BankBindApplyType.BIND);
                bankBindApply.setCardNo(cardNo);
                bankBindApply.setChannel(PayChannelEnum.HNA_PAY);
                bankBindApply.setName(idcardInfo.getName());
                bankBindApply.setCardType(CardTypeEnum.PAY);
                bankBindApply.setPhoneNo(phoneNo);
                bankBindApply.setCertificateNo(idcardInfo.getCardId().toUpperCase(Locale.ROOT));
                bankBindApply.setBankName(BankCardUtil.getBankNameByCardNo(cardNo));
                JSONObject request = new JSONObject();
                request.fluentPut("realName", idcardInfo.getName()).fluentPut("idcard", idcardInfo.getCardId()).fluentPut("cardNo", cardNo).fluentPut("phone",phoneNo);
                bankBindApply.setRequestBody(request.toJSONString());
                JSONObject jsonObject = BankUtil.card4Validate(bank4ValidateConfig, cardNo, idcardInfo.getCardId(),
                        idcardInfo.getName(), phoneNo);
                bankBindApply.setResultBody(jsonObject.toJSONString());
                bankBindApply.insert();
                int code = jsonObject.getIntValue("code");
                String msg = jsonObject.getString("msg");
                if (code != 200) {
                    log.error("绑定银行卡四要素认证失败，{}", jsonObject);
                    throw new GlobalRunTimeException(msg);
                }
                jsonObject = jsonObject.getJSONObject("data");
                code = jsonObject.getInteger("result");
                if (code > 0) {
                    log.error("绑定银行卡四要素认证失败，{}", jsonObject.get("msg"));
                    throw new GlobalRunTimeException(jsonObject.getString("msg"));
                }
                UserBankCard userBankCard = new UserBankCard();
                userBankCard.setUserId(bankBindApply.getUserId());
                userBankCard.setBankCardNo(bankBindApply.getCardNo());
                userBankCard.setBindId(bankBindApply.getId());
                userBankCard.setName(bankBindApply.getName());
                userBankCard.setMobile(bankBindApply.getPhoneNo());
                userBankCard.setChannel(PayChannelEnum.HNA_PAY);
                userBankCard.setIdentityNo(bankBindApply.getCertificateNo());
                userBankCard.setBankName(bankBindApply.getBankName());
                userBankCard.insert();
                bankBindApply.setStatus(BankBindApplyStatus.SUCCESS);
                bankBindApply.setBankCardId(userBankCard.getId());
                bankBindApply.updateById();
            } else {
                throw new GlobalRunTimeException("请稍后再试");
            }
        } catch (Exception e) {
            if (e instanceof GlobalRunTimeException) {
                throw new GlobalRunTimeException(e.getMessage());
            }
            throw new GlobalRunTimeException("请稍后再试");
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void hnaBindCardNew(UserInfo userInfo, String cardNo, String phoneNo) {
        RLock lock = redisson.getLock(RedisKeyEnum.USER_HNA_BIND_CARD_KEY.getKey() + PayChannelEnum.HNA_PAY.getCode() + cardNo);
        try{
            if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
                log.debug("----------------当前线程：{}", Thread.currentThread().getName());
                if(!userInfo.getIsSm()){
                    throw new GlobalRunTimeException(ErrorCode.PLEASE_PERFORM_REAL_NAME_AUTHENTICATION_FIRST);
                }
                IdcardInfo idcardInfo = idcardInfoDao.getByUserIdAndIsPass(userInfo.getId());
                if(ObjectUtil.isNull(idcardInfo)){
                    throw new GlobalRunTimeException("未查询到实名信息");
                }
                UserBankCard userCard = userBankCardDao.getByUserIdAndChannelAndCardNo(userInfo.getId(), PayChannelEnum.HNA_PAY, cardNo);
                if(ObjectUtil.isNotNull(userCard)){
                    throw new GlobalRunTimeException("该银行卡已绑定");
                }
                BankBindApply bankBindApply = new BankBindApply();
                bankBindApply.setUserId(userInfo.getId());
                bankBindApply.setStatus(BankBindApplyStatus.NOT_APPROVAL);
                bankBindApply.setApplyNo(OrderNoUtil.getOrderNo());
                bankBindApply.setType(BankBindApplyType.BIND);
                bankBindApply.setCardNo(cardNo);
                bankBindApply.setChannel(PayChannelEnum.HNA_PAY);
                bankBindApply.setName(idcardInfo.getName());
                bankBindApply.setCardType(CardTypeEnum.PAY);
                bankBindApply.setPhoneNo(phoneNo);
                bankBindApply.setCertificateNo(idcardInfo.getCardId().toUpperCase(Locale.ROOT));
                bankBindApply.setBankName(BankCardUtil.getBankNameByCardNo(cardNo));
                JSONObject request = new JSONObject();
                request.fluentPut("realName", idcardInfo.getName()).fluentPut("idcard", idcardInfo.getCardId()).fluentPut("cardNo", cardNo).fluentPut("phone",phoneNo);
                bankBindApply.setRequestBody(request.toJSONString());
//                JSONObject jsonObject = BankUtil.card4Validate(bank4ValidateConfig, cardNo, idcardInfo.getCardId(),
//                        idcardInfo.getName(), phoneNo);
                Card4ValidateVO card4ValidateVO = shuJuBaoCloudFeign.card4Validate(Card4ValidateDTO.build(idcardInfo.getName(), idcardInfo.getCardId(), phoneNo, cardNo));
                if (ObjectUtil.isNull(card4ValidateVO)) {
                    throw new GlobalRunTimeException("绑定银行卡四要素认证失败");
                }
                bankBindApply.setResultBody(JSONObject.toJSONString(card4ValidateVO));
                bankBindApply.insert();
                if (!"10000".equals(card4ValidateVO.getCode())) {
                    log.error("绑定银行卡四要素认证失败，{}", card4ValidateVO);
                    throw new GlobalRunTimeException(card4ValidateVO.getMessage());
                }
                String state = card4ValidateVO.getData().getState();
                //1:验证一致，2:验证不一致，3:异常情况
                if ("2".equals(state)) {
                    log.error("绑定银行卡四要素认证失败state，{}", card4ValidateVO.getData().getState());
                    throw new GlobalRunTimeException("验证结果不一致");
                }else if ("3".equals(state)) {
                    log.error("绑定银行卡四要素认证失败state，{}", card4ValidateVO.getData().getState());
                    throw new GlobalRunTimeException("绑定银行卡四要素认证失败");
                }else if ("1".equals(state)) {
                    UserBankCard userBankCard = new UserBankCard();
                    userBankCard.setUserId(bankBindApply.getUserId());
                    userBankCard.setBankCardNo(bankBindApply.getCardNo());
                    userBankCard.setBindId(bankBindApply.getId());
                    userBankCard.setName(bankBindApply.getName());
                    userBankCard.setMobile(bankBindApply.getPhoneNo());
                    userBankCard.setChannel(PayChannelEnum.HNA_PAY);
                    userBankCard.setIdentityNo(bankBindApply.getCertificateNo());
                    userBankCard.setBankName(bankBindApply.getBankName());
                    userBankCard.insert();
                    bankBindApply.setStatus(BankBindApplyStatus.SUCCESS);
                    bankBindApply.setBankCardId(userBankCard.getId());
                    bankBindApply.updateById();
                }
            } else {
                throw new GlobalRunTimeException("请稍后再试");
            }
        } catch (Exception e) {
            if (e instanceof GlobalRunTimeException) {
                throw new GlobalRunTimeException(e.getMessage());
            }
            throw new GlobalRunTimeException("请稍后再试");
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }


}